/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package jsr203.nio.file;

import java.io.IOError;
import java.io.IOException;
import java.util.regex.Pattern;
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import jsr203.sun.nio.fs.Globs;

/**
 * Defines factory methods for directory stream filters and utility methods to
 * combine filters into simple expressions.
 *
 * @since 1.7
 */

public final class DirectoryStreamFilters {
    private DirectoryStreamFilters() { }

    /**
     * Constructs a directory stream filter that filters directory entries by
     * matching the {@code String} representation of their file names against a
     * given case sensitive <em>glob</em>.
     *
     * <p> The expression language supported by this method is exactly as
     * specified by the {@link FileSystem#getNameMatcher
     * getNameMatcher} method for the "glob" syntax.
     *
     * @param   glob
     *          The pattern to match file names
     *
     * @return  A new directory stream filter
     *
     * @throws  java.util.regex.PatternSyntaxException
     *          If the pattern is invalid
     */
    public static DirectoryStream.Filter newCaseSensitiveGlobFilter(String glob) {
        String regex = Globs.toRegexPattern(glob);
        final Pattern pattern = Pattern.compile(regex);
        return new DirectoryStream.Filter() {
            @Override
            public boolean accept(DirectoryEntry entry)  {
                return pattern.matcher(entry.getName().toString()).matches();
            }
        };
    }

    /**
     * Constructs a directory stream filter that filters directory entries by
     * matching the {@code String} representation of their file names against a
     * given case insensitive <em>glob</em>.
     *
     * <p> The expression language supported by this method is exactly as
     * specified by the {@link FileSystem#getNameMatcher getNameMatcher} method
     * for the "glob" syntax.
     *
     * @param   glob
     *          The pattern to match file names
     *
     * @return  A new directory stream filter
     *
     * @throws  java.util.regex.PatternSyntaxException
     *          If the pattern is invalid
     */
    public static DirectoryStream.Filter newCaseInsensitiveGlobFilter(String glob) {
        String regex = Globs.toRegexPattern(glob);
        final Pattern pattern = Pattern.compile(regex,
            (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE));
        return new DirectoryStream.Filter() {
            @Override
            public boolean accept(DirectoryEntry entry)  {
                return pattern.matcher(entry.getName().toString()).matches();
            }
        };
    }

    /**
     * Constructs a directory stream filter that filters directory entries by
     * their  <a href="http://www.ietf.org/rfc/rfc2045.txt">MIME</a> content
     * type. The directory stream filter's {@link
     * jsr203.nio.file.DirectoryStream.Filter#accept accept} method returns {@code
     * true} if the content type of the directory entry can be determined by
     * invoking the {@link Files#probeContentType probeContentType} method, and
     * the content type matches the given content type.
     *
     * <p> The {@code type} parameter is the value of a Multipurpose Internet
     * Mail Extension (MIME) content type as defined by <a
     * href="http://www.ietf.org/rfc/rfc2045.txt"><i>RFC&nbsp;2045: Multipurpose
     * Internet Mail Extensions (MIME) Part One: Format of Internet Message
     * Bodies</i></a>. It is parsable according to the grammar in the RFC. Any
     * space characters (<code>'&#92;u0020'</code>) surrounding its components are
     * ignored. Content types are compared by matching the primary and subtype
     * components. Parameters, if any, are ignored. The primary type matches if
     * it has the value {@code '*'} or is equal to the directory entry's primary
     * type without regard to case. The subtype matches if has the value {@code
     * '*'} or is equal to the directory entry's subtype without regard to
     * case. If both the primary and subtype match then the filter's {@code
     * accept} method returns {@code true}.
     *
     * <p> The {@code accept} method of the resulting directory stream filter
     * throws {@link IOError} if the probing of the content type fails by
     * throwing an {@link IOException}.
     *
     * <p> <b>Usage Example:</b>
     * Suppose we require to list only the HTML files in a directory.
     * <pre>
     *     DirectoryStream.Filter filter =
     *         DirectoryStreamFilters.newContentTypeFilter("text/html");
     * </pre>
     *
     * @param   type
     *          The content type
     *
     * @return  A new directory stream filter
     *
     * @throws  IllegalArgumentException
     *          If the {@code type} parameter cannot be parsed as a MIME type
     */
    public static DirectoryStream.Filter newContentTypeFilter(String type) {
        final MimeType matchType;
        try {
            matchType = new MimeType(type);
        } catch (MimeTypeParseException x) {
            throw new IllegalArgumentException(x);
        }
        return new DirectoryStream.Filter() {
            @Override
            public boolean accept(DirectoryEntry entry) {
                String fileType;
                try {
                    fileType = Files.probeContentType(entry);
                } catch (IOException x) {
                    throw new IOError(x);
                }
                if (fileType != null) {
                    try {
                        if (matchType.match(fileType))
                            return true;
                    } catch (MimeTypeParseException x) {
                        // if this happen it means there is a FileTypeDetector
                        // installed that is return invalid content types.
                    }
                }
                return false;
            }
        };
    }

    /**
     * Returns a directory stream filter that {@link
     * jsr203.nio.file.DirectoryStream.Filter#accept accepts} a directory entry
     * if the entry is accepted by all of the given filters.
     *
     * <p> This method returns a filter that invokes, in turn, the {@code accept}
     * method of each of the given filters. If {@code false} is returned by any
     * of the filters then the directory entry is filtered. If the directory
     * entry is not filtered then the resulting filter accepts the entry.
     * If the array of filters contains zero elements then the resulting
     * filter accepts all directory entries.
     *
     * @param   filters
     *          The array of filters
     *
     * @return  The resulting filter
     */
    public static DirectoryStream.Filter allOf(final DirectoryStream.Filter... filters) {
        return new DirectoryStream.Filter() {
            @Override
            public boolean accept(DirectoryEntry entry) {
                for (DirectoryStream.Filter filter: filters) {
                    if (!filter.accept(entry))
                        return false;
                }
                return true;
            }
        };
    }

    /**
     * Returns a directory stream filter that {@link
     * jsr203.nio.file.DirectoryStream.Filter#accept accepts} a directory entry
     * if the entry is accepted by one or more of the given filters.
     *
     * <p> This method returns a filter that invokes, in turn, the {@code accept}
     * method of each of the given filters. If {@code true} is returned by any
     * of the filters then the directory entry is accepted. If none of the
     * filters accepts the directory entry then it is filtered. If the array of
     * filters contains zero elements then the resulting filter will filter all
     * directory entries.
     *
     * @param   filters
     *          The array of filters
     *
     * @return  The resulting filter
     */
    public static DirectoryStream.Filter anyOf(final DirectoryStream.Filter... filters) {
        return new DirectoryStream.Filter() {
            @Override
            public boolean accept(DirectoryEntry entry) {
                for (DirectoryStream.Filter filter: filters) {
                    if (filter.accept(entry))
                        return true;
                }
                return false;
            }
        };
    }

    /**
     * Returns a directory stream filter that is the <em>complement</em> of the
     * given filter. The resulting filter {@link
     * jsr203.nio.file.DirectoryStream.Filter#accept accepts} a directory entry
     * if filtered by the given filter, and filters any entries that are accepted
     * by the given filter.
     *
     * @param   filter
     *          The given filter
     *
     * @return  The resulting filter that is the complement of the given filter
     */
    public static DirectoryStream.Filter complementOf(final DirectoryStream.Filter filter) {
        return new DirectoryStream.Filter() {
            @Override
            public boolean accept(DirectoryEntry entry) {
                return !filter.accept(entry);
            }
        };
    }
}
